from typing import List


from .MethodCall import AndOperatorMethodCall, EqualOperatorMethodCall, GetItemMethodCall, MethodCall, OrOperatorMethodCall
import uuid

from ..helper.utils import CanJson


class Statement(object):
    def __init__(self,  caller_name: str, ret_name: str = None) -> None:
        self.calls: List[MethodCall] = []
        self.caller_name = caller_name
        self.ret_name = ret_name

    def __getattr__(self, name):
        call = MethodCall(name)
        self.addCall(call)
        return self

    def __call__(self, *args, **kwargs):
        self.calls[-1].set_args(*args, **kwargs)
        return self

    def __getitem__(self, key):
        call = GetItemMethodCall()
        self.addCall(call)
        call.set_args(key)
        return self

    def __or__(self, other):
        call = OrOperatorMethodCall()
        self.addCall(call)
        call.set_args(other)
        return self

    def __and__(self, other):
        call = AndOperatorMethodCall()
        self.addCall(call)
        call.set_args(other)
        return self

    def __eq__(self, other):
        call = EqualOperatorMethodCall()
        self.addCall(call)
        call.set_args(other)
        return self

    def addCall(self, call: MethodCall):
        self.calls.append(call)

    def is_empty(self) -> bool:
        return len(self.calls) == 0

    def has_exception(self):
        ret = any(c.has_ex for c in self.calls)
        return ret

    def get_calls(self):
        return (c for c in self.calls if c.name != '__iter__')

    def to_code(self) -> str:
        codes = (
            c.to_code()
            for c in self.calls
        )

        code = ''.join(codes)

        if self.ret_name:
            return f'{self.ret_name} = {self.caller_name}{code}'

        return f'{self.caller_name}{code}'

    def run(self, var_dict):

        obj = var_dict[self.caller_name]

        for c in self.get_calls():
            try:
                obj = c.run(obj, var_dict=var_dict)
            except Exception as ex:
                c.set_exception(ex)
                raise

        return obj


class Commander(CanJson):

    def to_json_dict(self):
        ret = self.__dict__.copy()
        del ret['statements']
        ret.update({'hasError': self.has_exception()})
        ret.update({'code': self.to_code()})
        return ret

    def __init__(self, name: str) -> None:
        self._id = uuid.uuid1()
        self.name = name
        self.statements: List[Statement] = []

    def __enter__(self) -> 'Commander':
        return self

    def __exit__(self, type, value, trace):
        pass

    @property
    def id(self):
        return self._id

    def create_statement(self, caller_name: str, ret_name: str = None) -> Statement:
        st = Statement(caller_name, ret_name)
        self.addStatement(st)
        return st

    def get_last_statement(self) -> Statement:
        return self.statements[-1]

    def has_exception(self):
        ret = any(s.has_exception() for s in self.statements)
        return ret

    def is_empty(self) -> bool:
        return len(self.statements) == 0

    def set_name(self, name: str):
        self.name = name

    def addStatement(self, statement: Statement):
        self.statements.append(statement)

    def to_code(self) -> str:
        codes = (
            c.to_code()
            for c in self.statements
        )
        code = '\n'.join(codes)

        return code


class CommanderManager():

    def __init__(self) -> None:
        self._cmds: List[Commander] = []
        self.reset_cmds()
        self.var_dict = {}

    def get_last_cmd(self) -> Commander:
        if len(self._cmds) == 0:
            return None
        return self._cmds[-1]

    def get_cmds(self):
        return (c for c in self._cmds if not c.is_empty())

    def addVar(self, var_name: str, ret):
        self.var_dict[var_name] = ret

    def getVar(self, var_name: str):
        return self.var_dict[var_name]

    def get_last_used_var_ret(self):
        key = list(self.var_dict.keys())[-1]
        return self.getVar(key)

    def clear_all_var(self):
        self.var_dict.clear()

    def clear_all(self):
        self._cmds.clear()
        self.clear_all_var()

    def append(self, cmd: Commander):
        self._cmds.append(cmd)

    def reset_cmds(self):
        self._cmds: List[Commander] = []

    def remove_cmd(self, id: uuid.UUID):
        idxs = [i for i, t in enumerate(self._cmds) if t.id == id]
        assert (len(idxs) in [0, 1]), f'多个 cmd[id={id}] 符合条件'
        if len(idxs) > 0:
            del self._cmds[idxs[0]]
